from django.contrib import admin
from django.utils import timezone

import functools
import json
import time
import textwrap
from ygmsp_dj.settings import SECRET_KEY
from utils.vault import decrypt
# Azure
import msrestazure.azure_cloud
from azure.identity import ClientSecretCredential
from azure.mgmt.resource import SubscriptionClient
from azure.mgmt.resourcegraph import ResourceGraphClient
from azure.mgmt.resourcegraph.models import QueryRequest, QueryRequestOptions

from . import models, filters

import logging
logger = logging.getLogger(__name__)
logger.success = lambda msg, *args, **kwargs: logger.log(25, msg, *args, **kwargs)


@functools.lru_cache(maxsize=8)
def decrypt_lru_cache(*args, **kwargs):
    """函数记忆"""
    logger.debug('密文未缓存需解密: 密文的第一行=%s', (kwargs.get('fmt_vaulttext') or args[0]).splitlines()[1])
    return decrypt(*args, **kwargs)

# Register your models here.
class AuthAdmin(admin.ModelAdmin):
    list_display = ('name', 'display_name', 'cloud', 'client_id', 'tenant_id')
    fieldsets = (
        (None, {
            'fields': ('name', 'display_name', 'cloud', 'client_id', 'tenant_id')
        }),
        ('Secret', {
            'classes': ('collapse',),
            'fields': ('client_secret',)
        })
    )

    actions = ['get_subscriptions', 'message_level_style']

    @admin.action(description='发送测试消息')
    def message_level_style(self, request, queryset):
        self.message_user(request, 'Message debug', 'debug')
        self.message_user(request, 'Message info', 'info')
        self.message_user(request, 'Message success', 'success')
        self.message_user(request, 'Message warning', 'warning')
        self.message_user(request, 'Message error', 'error')

    @admin.action(description='同步订阅')
    def get_subscriptions(self, request, queryset):
        for auth in queryset:
            start_time = time.time()
            client_secret = decrypt_lru_cache(auth.client_secret, SECRET_KEY)
            duration_time_decrypt = time.time() - start_time
            cred = ClientSecretCredential(auth.tenant_id, auth.client_id, client_secret)
            cloud = getattr(msrestazure.azure_cloud, auth.cloud)
            client = SubscriptionClient(cred, 
                base_url=cloud.endpoints.resource_manager, 
                credential_scopes=[cloud.endpoints.resource_manager + "/.default"])
            start_time = time.time()
            start_time_report = True
            sub_obj_count = 0
            for sub in client.subscriptions.list():
                if start_time_report:
                    duration_time_request = time.time() - start_time
                    logger.debug(f'请求订阅信息: {auth} 耗时={duration_time_request:.2f}秒')
                    start_time_report = False
                # 代码参考这里: https://docs.djangoproject.com/zh-hans/4.0/ref/models/querysets/#get-or-create
                # 不需要操作原子性，并且使用手动主键也用不了，不用get_or_create()
                try:
                    sub_obj = models.Subscriptions.objects.get(id=sub.subscription_id)
                    if sub.display_name != sub_obj.name:
                        sub_obj.old_name = sub_obj.name
                        sub_obj.name = sub.display_name
                        sub_obj.save()
                        logger.debug(f'订阅有更新: {sub_obj.id} {sub_obj.old_name} > {sub_obj.name}')
                except models.Subscriptions.DoesNotExist:
                    sub_obj = models.Subscriptions(id=sub.subscription_id, name=sub.display_name, auth=auth)
                    sub_obj.save()
                    logger.debug(f'发现新的订阅: {auth} {sub_obj.id} {sub_obj.name}')
                    sub_obj_count += 1
                    if sub_obj_count <= 3:
                        self.message_user(
                            request, 
                            f'发现新的订阅 {auth} {sub.display_name} {sub.subscription_id}',
                            'success')
            self.message_user(
                request, 
                f'{auth} 一共更新了 {sub_obj_count} 个订阅 密文解密时间 {duration_time_decrypt:.2f}秒 请求时间 {duration_time_request:.2f}秒')


class SubscriptionsAdmin(admin.ModelAdmin):
    list_display = ('name', 'id', 'ctime', 'uptime', 'auth_display')

    @admin.display(description='auth')
    def auth_display(self, obj):
        return obj.auth.display_name

    list_filter = ('auth__display_name',)
    search_fields = ('name',)

    actions = ['get_resources']

    def _update_or_create_resource(self, request, res, res_id_list, new_res_list, subscription):
        """在get_resources中更新或者新建记录的逻辑"""
        # Azure 的资源id中的部分内容，比如资源组名称大小写会随机变的
        res_id = res['id'].lower()
        try:
            res_obj = models.Resources.objects.get(id=res_id)
            res_id_list.append(res_id)
            if res_obj.del_time:
                # 已经删除的资源又重复创建
                res_obj.delete()
                raise models.Resources.DoesNotExist
            # 调用models中自定义的方式比较有变化才会save
            changed_keys = res_obj.save_by_resource_graph(res)
            if changed_keys:
                self.message_user(
                    request, 
                    f'{res_obj.id} 更新字段: {" ".join(changed_keys)}',
                    'success')
        except models.Resources.DoesNotExist:
            res_obj = models.Resources(
                id = res_id,
                name = res['name'],
                type = res['type'],
                tenant_id = res['tenantId'],
                kind = res['kind'],
                location = res['location'],
                resource_group = res['resourceGroup'],
                managed_by = res['managedBy'],
                sku = json.dumps(res['sku']),
                plan = json.dumps(res['plan']),
                properties = json.dumps(res['properties']),
                tags = json.dumps(res['tags']),
                identity = json.dumps(res['identity']),
                subscription = subscription,
            )
            new_res_list.append(res_obj)

    @admin.action(description='同步资源')
    def get_resources(self, request, queryset):
        str_query = textwrap.dedent('''\
            Resources
            | order by id asc
            ''')
        for subscription in queryset:
            start_time = time.time()
            subscription_id = subscription.id
            auth = subscription.auth
            client_secret = decrypt_lru_cache(auth.client_secret, SECRET_KEY)
            cred = ClientSecretCredential(auth.tenant_id, auth.client_id, client_secret)
            cloud = getattr(msrestazure.azure_cloud, auth.cloud)
            client = ResourceGraphClient(cred, 
                base_url=cloud.endpoints.resource_manager,
                credential_scopes=[cloud.endpoints.resource_manager + "/.default"])
            
            new_res_list = []  # 用以批量创建记录
            res_id_list = []   # 用以检查已删除资源
            # 创建新资源前，获取到已经存在的资源信息，只要当前订阅并且是没有del_time的（当前订阅的未删除资源）
            # QuerySet 是惰性的，调用list()强制执行，确保拿出当前的结果，而不是创建了新资源后的结果
            res_in_db = list(models.Resources.objects.filter(subscription=subscription, del_time__isnull=True))

            arg_query = QueryRequest(query=str_query, subscriptions=[subscription_id])
            resp = client.resources(arg_query)
            resources = resp.data
            for res in resources:
                self._update_or_create_resource(request, res, res_id_list, new_res_list, subscription)
            while resp.skip_token:
                arg_query_options = QueryRequestOptions(skip_token=resp.skip_token)
                arg_query = QueryRequest(query=str_query, subscriptions=[subscription_id], options=arg_query_options)
                resp = client.resources(arg_query)
                resources = resp.data
                for res in resources:
                    self._update_or_create_resource(request, res, res_id_list, new_res_list, subscription)

            duration_time_request = time.time() - start_time
            if new_res_list:
                models.Resources.objects.bulk_create(new_res_list)
                logger.info(f'批量创建资源记录: {subscription.id}({subscription.name}) 数量={len(new_res_list)}')
                duration_time_bulk_create = time.time() - start_time
                self.message_user(
                    request, 
                    f'{subscription.name} 新增了 {len(new_res_list)} 个资源记录 请求耗时 {duration_time_request:.2f}秒 '
                    f'再算上添加记录的总耗时 {duration_time_bulk_create:.2f}秒')
            else:
                self.message_user(
                    request, 
                    f'{subscription.name} 无新增资源 请求耗时 {duration_time_request:.2f}秒')
            # 检查已删除资源
            res_del = []
            now = None
            for res in res_in_db:
                if res.id not in res_id_list:
                    now = now or timezone.now()
                    res.del_time = now
                    res_del.append(res)
                    logger.warning('发现已删除资源: {}'.format(' '.join(
                        [f'"{i}"' if ' ' in i else i for i in [res.subscription.auth.name, res.subscription.name, res.resource_group, res.type, res.name]]
                    )))
                    self.message_user(
                        request,
                        '发现已删除资源 {}'.format(' '.join(
                            [f'"{i}"' if ' ' in i else i for i in [res.subscription.auth.name, res.subscription.name, res.resource_group, res.type, res.name]]
                        )),
                        'warning')
            if res_del:
                models.Resources.objects.bulk_update(res_del, ['del_time'])


class ResourcesAdmin(admin.ModelAdmin):
    list_display = ('name', 'type', 'auth', 'subscription', 'location', 'resource_group', 'ctime', 'uptime', 'del_time')

    @admin.display(description='auth')
    def auth(self, obj):
        return obj.subscription.auth.display_name

    search_fields = ('name',)
    list_filter = (
        filters.ResourceIsDeletedListFilter,
        filters.ResourceAuthListFilter, filters.ResourceSubscriptionListFilter, 
        filters.ResourceGroupListFilter, filters.ResourceTypeListFilter,
        'ctime', 'del_time')


admin.site.register(models.Auth, AuthAdmin)
admin.site.register(models.Subscriptions, SubscriptionsAdmin)
admin.site.register(models.Resources, ResourcesAdmin)

